/**
 * Template parsing utilities for variable substitution
 */

import type { ParameterValues, VariableSyntax } from '../types';

/**
 * Options for template parsing
 */
export interface TemplateParseOptions {
  supportedSyntax?: VariableSyntax[];
  strictMode?: boolean;
  allowUndefined?: boolean;
}

/**
 * Default template parse options
 */
const DEFAULT_OPTIONS: Required<TemplateParseOptions> = {
  supportedSyntax: ['{{}}', '${}', '@{}'],
  strictMode: true,
  allowUndefined: false
};

/**
 * Parses a template string and replaces variables with parameter values
 * @param template - Template string to parse
 * @param parameters - Parameter values for substitution
 * @param options - Parsing options
 * @returns Processed template string
 */
export function parseTemplate(
  template: string,
  parameters: ParameterValues,
  options: TemplateParseOptions = {}
): string {
  const opts = { ...DEFAULT_OPTIONS, ...options };
  let result = template;

  // Process each supported syntax
  for (const syntax of opts.supportedSyntax) {
    result = processSyntax(result, parameters, syntax, opts);
  }

  // Check for unresolved variables in strict mode
  if (opts.strictMode) {
    const unresolvedVars = findUnresolvedVariables(result, opts.supportedSyntax);
    if (unresolvedVars.length > 0) {
      throw new Error(`Unresolved variables found: ${unresolvedVars.join(', ')}`);
    }
  }

  return result;
}

/**
 * Processes a specific variable syntax
 * @param template - Template string
 * @param parameters - Parameter values
 * @param syntax - Variable syntax to process
 * @param options - Parsing options
 * @returns Processed template string
 */
function processSyntax(
  template: string,
  parameters: ParameterValues,
  syntax: VariableSyntax,
  options: Required<TemplateParseOptions>
): string {
  const regex = getSyntaxRegex(syntax);
  
  return template.replace(regex, (match, variableName) => {
    // Handle different syntax types
    switch (syntax) {
      case '{{}}':
        return processHandlebarsVariable(variableName, parameters, options);
      case '${}':
        return processExpressionVariable(variableName, parameters, options);
      case '@{}':
        return processDirectVariable(variableName, parameters, options);
      default:
        return match;
    }
  });
}

/**
 * Gets regex pattern for a specific syntax
 * @param syntax - Variable syntax
 * @returns Regular expression for matching variables
 */
function getSyntaxRegex(syntax: VariableSyntax): RegExp {
  switch (syntax) {
    case '{{}}':
      return /\{\{([^}]+)\}\}/g;
    case '${}':
      return /\$\{([^}]+)\}/g;
    case '@{}':
      return /@\{([^}]+)\}/g;
    default:
      throw new Error(`Unsupported syntax: ${syntax}`);
  }
}

/**
 * Processes handlebars-style variables ({{variable}})
 * @param variableName - Variable name
 * @param parameters - Parameter values
 * @param options - Parsing options
 * @returns Processed value
 */
function processHandlebarsVariable(
  variableName: string,
  parameters: ParameterValues,
  options: Required<TemplateParseOptions>
): string {
  const trimmedName = variableName.trim();
  
  if (trimmedName in parameters) {
    return formatValue(parameters[trimmedName]);
  }
  
  if (options.allowUndefined) {
    return `{{${variableName}}}`;
  }
  
  throw new Error(`Variable '${trimmedName}' not found in parameters`);
}

/**
 * Processes expression-style variables (${expression})
 * @param expression - Expression to evaluate
 * @param parameters - Parameter values
 * @param options - Parsing options
 * @returns Processed value
 */
function processExpressionVariable(
  expression: string,
  parameters: ParameterValues,
  options: Required<TemplateParseOptions>
): string {
  const trimmedExpression = expression.trim();
  
  // Simple variable reference
  if (isSimpleVariable(trimmedExpression)) {
    if (trimmedExpression in parameters) {
      return formatValue(parameters[trimmedExpression]);
    }
    
    if (options.allowUndefined) {
      return `\${${expression}}`;
    }
    
    throw new Error(`Variable '${trimmedExpression}' not found in parameters`);
  }
  
  // Complex expression evaluation (basic support)
  try {
    const result = evaluateExpression(trimmedExpression, parameters);
    return formatValue(result);
  } catch (error) {
    if (options.allowUndefined) {
      return `\${${expression}}`;
    }
    throw new Error(`Failed to evaluate expression '${trimmedExpression}': ${error instanceof Error ? error.message : 'Unknown error'}`);
  }
}

/**
 * Processes direct value variables (@{value})
 * @param value - Direct value
 * @param parameters - Parameter values (unused for direct values)
 * @param options - Parsing options
 * @returns Processed value
 */
function processDirectVariable(
  value: string,
  _parameters: ParameterValues,
  _options: Required<TemplateParseOptions>
): string {
  const trimmedValue = value.trim();
  
  // Try to parse as JSON for complex values
  try {
    const parsed = JSON.parse(trimmedValue);
    return formatValue(parsed);
  } catch {
    // Return as string if not valid JSON
    return trimmedValue;
  }
}

/**
 * Checks if an expression is a simple variable reference
 * @param expression - Expression to check
 * @returns True if it's a simple variable
 */
function isSimpleVariable(expression: string): boolean {
  return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(expression);
}

/**
 * Evaluates a simple expression with parameter substitution
 * @param expression - Expression to evaluate
 * @param parameters - Parameter values
 * @returns Evaluation result
 */
function evaluateExpression(expression: string, parameters: ParameterValues): unknown {
  // Basic expression evaluation - replace variables and evaluate
  let processedExpression = expression;
  
  // Replace variable references
  for (const [name, value] of Object.entries(parameters)) {
    const regex = new RegExp(`\\b${name}\\b`, 'g');
    processedExpression = processedExpression.replace(regex, JSON.stringify(value));
  }
  
  // Simple arithmetic and string operations only
  if (/^[\d\s+\-*/().]+$/.test(processedExpression)) {
    // Safe arithmetic evaluation
    return Function(`"use strict"; return (${processedExpression})`)();
  }
  
  // String concatenation
  if (processedExpression.includes('+') && processedExpression.includes('"')) {
    return Function(`"use strict"; return (${processedExpression})`)();
  }
  
  throw new Error('Complex expressions not supported');
}

/**
 * Formats a value for template substitution
 * @param value - Value to format
 * @returns Formatted string
 */
function formatValue(value: unknown): string {
  if (value === null || value === undefined) {
    return '';
  }
  
  if (typeof value === 'string') {
    return value;
  }
  
  if (typeof value === 'number' || typeof value === 'boolean') {
    return String(value);
  }
  
  // For objects and arrays, return JSON representation
  return JSON.stringify(value);
}

/**
 * Finds unresolved variables in a template
 * @param template - Template string to check
 * @param syntaxes - Variable syntaxes to check for
 * @returns Array of unresolved variable names
 */
function findUnresolvedVariables(template: string, syntaxes: VariableSyntax[]): string[] {
  const unresolved: string[] = [];
  
  for (const syntax of syntaxes) {
    const regex = getSyntaxRegex(syntax);
    let match;
    
    while ((match = regex.exec(template)) !== null) {
      unresolved.push(match[0]); // Full match including syntax
    }
  }
  
  return unresolved;
}

/**
 * Extracts all variable names from a template
 * @param template - Template string to analyze
 * @param syntaxes - Variable syntaxes to look for
 * @returns Array of unique variable names
 */
export function extractVariables(template: string, syntaxes: VariableSyntax[] = ['{{}}', '${}']): string[] {
  const variables = new Set<string>();
  
  for (const syntax of syntaxes) {
    const regex = getSyntaxRegex(syntax);
    let match;
    
    while ((match = regex.exec(template)) !== null) {
      const variableName = match[1]?.trim();
      if (variableName && isSimpleVariable(variableName)) {
        variables.add(variableName);
      }
    }
  }
  
  return Array.from(variables);
}
